#include <stdint.h>
#include <ctl_api.h>
#include <LPC11xx.h>

#define	I2CONSET_I2EN	(0x1<<6)
#define	I2CONSET_AA	(0x1<<2)
#define	I2CONSET_SI	(0x1<<3)
#define	I2CONSET_STO	(0x1<<4)
#define	I2CONSET_STA	(0x1<<5)

#define	I2CONCLR_AAC	(0x1<<2)
#define	I2CONCLR_SIC	(0x1<<3)
#define	I2CONCLR_STAC	(0x1<<5)
#define	I2CONCLR_I2ENC	(0x1<<6)

#define	I2SCLH_SCLH	0x180
#define	I2SCLL_SCLL	0x180

#define	I2CST_IDLE	0
#define	I2CST_STARTED	1
#define	I2CST_RESTARTED	2
#define	I2CST_REPEATED_START	3
#define	I2CST_DATA_ACK	4
#define	I2CST_DATA_NACK	5
#define	I2CST_BUSY	6
#define	I2CST_NO_DATA	7
#define	I2CST_NACK_ON_ADDRESS	8
#define	I2CST_NACK_ON_DATA	9
#define	I2CST_ARBITRATION_LOST	10
#define	I2CST_TIMEOUT	11
#define	I2CST_OK	12

#define NBSIZE  4

typedef struct {
  short len;
  short remain;
  short boffset;
  short state;
  uint8_t buffer[NBSIZE];
} i2cinfo_t;

i2cinfo_t i2cinfo;
CTL_SEMAPHORE_t sem_i2c;

/* This interrupt handler handles master mode transfer only */
void I2C_IRQHandler(void)
{
  uint8_t stat;
  i2cinfo_t *i2cInfo = &i2cinfo;

  ctl_enter_isr();

  stat = LPC_I2C->STAT;

  switch (stat) {
  case 0x08:	/* Start is issued */
    i2cInfo->boffset = 0;
    i2cInfo->remain = i2cInfo->len;
    LPC_I2C->DAT = i2cInfo->buffer[i2cInfo->boffset++];
    LPC_I2C->CONCLR = (I2CONCLR_SIC | I2CONCLR_STAC);
    i2cInfo->state = I2CST_STARTED;
    break;
  case 0x10:	/* Repeated started */
    LPC_I2C->DAT = i2cInfo->buffer[i2cInfo->boffset++];
    LPC_I2C->CONCLR = (I2CONCLR_SIC | I2CONCLR_STAC);
    i2cInfo->state = I2CST_REPEATED_START;
    break;
  case 0x18:	/* Got ACK */
    if (i2cInfo->len == 1) {
      LPC_I2C->CONSET = I2CONSET_STO;	/* Set Stop flag */
      i2cInfo->state = I2CST_NO_DATA;
      ctl_semaphore_signal(&sem_i2c);
    } else {
      LPC_I2C->DAT = i2cInfo->buffer[i2cInfo->boffset++];
    }
    LPC_I2C->CONCLR = I2CONCLR_SIC;
    break;
  case 0x28:	/* Data byte has been transmitted */
    if (i2cInfo->boffset < i2cInfo->len) {
      LPC_I2C->DAT = i2cInfo->buffer[i2cInfo->boffset++];
    } else {
      LPC_I2C->CONSET = I2CONSET_STO;	/* Set stop flag */
      ctl_semaphore_signal(&sem_i2c);
    }
    LPC_I2C->CONCLR = I2CONCLR_SIC;
    break;
  case 0x30:
    LPC_I2C->CONSET = I2CONSET_STO;
    i2cInfo->state = I2CST_NACK_ON_DATA;
    LPC_I2C->CONCLR = I2CONCLR_SIC;
    ctl_semaphore_signal(&sem_i2c);
    break;
  default:
    i2cInfo->state = I2CST_ARBITRATION_LOST;
    LPC_I2C->CONCLR = I2CONCLR_SIC;
    ctl_semaphore_signal(&sem_i2c);
    break;
  }

  ctl_exit_isr();
}

void i2c_write_reg(uint8_t devaddr, uint8_t regno, uint8_t bdata)
{
  i2cinfo_t *i2cInfo = &i2cinfo;

  i2cInfo->state = I2CST_IDLE;
  i2cInfo->len = 3;
  i2cInfo->buffer[0] = (devaddr << 1);
  i2cInfo->buffer[1] = regno;
  i2cInfo->buffer[2] = bdata;

  LPC_I2C->CONSET = I2CONSET_STA;	/* Set start flag */

  ctl_semaphore_wait(&sem_i2c, CTL_TIMEOUT_NONE, 0);
}

void i2c_init(void)
{
  ctl_semaphore_init(&sem_i2c, 0);

  LPC_IOCON->PIO0_4 &= ~0x3f;		/* SCL */
  LPC_IOCON->PIO0_4 |= 0x01;		/* SCL */
  LPC_IOCON->PIO0_5 &= ~0x3f;		/* SDA */
  LPC_IOCON->PIO0_5 |= 0x01;		/* SDA */

  LPC_SYSCON->SYSAHBCLKCTRL |= (1<< 5);	/* Enable I2C clock */

  LPC_SYSCON->PRESETCTRL |= (0x01 << 1);/* Reset I2C */

  LPC_I2C->CONCLR = I2CONCLR_AAC | I2CONCLR_SIC | I2CONCLR_STAC | I2CONCLR_I2ENC;

  LPC_I2C->SCLL = I2SCLL_SCLL;
  LPC_I2C->SCLH = I2SCLH_SCLH;

  ctl_unmask_isr(I2C_IRQn);

  LPC_I2C->CONSET = I2CONSET_I2EN;	/* Master mode */
}
